# Copyright 2010 Damian Powazka. All rights reserved.
#
#Redistribution and use in source and binary forms, with or without modification, are
#permitted provided that the following conditions are met:
#
#   1. Redistributions of source code must retain the above copyright notice, this list of
#      conditions and the following disclaimer.
#
#   2. Redistributions in binary form must reproduce the above copyright notice, this list
#      of conditions and the following disclaimer in the documentation and/or other materials
#      provided with the distribution.
#
#THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND ANY EXPRESS OR IMPLIED
#WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
#FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR
#CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
#CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
#SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
#ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
#NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
#ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
#The views and conclusions contained in the software and documentation are those of the
#authors and should not be interpreted as representing official policies, either expressed
#or implied, of Damian Powazka.

import sys
import logging
from optparse import OptionParser
from django.core.management import setup_environ
 
#TODO: ManyToMany - send list of URLs.
#TODO: Use pep-8 formater

# Admin generation:
#TODO: Add view - Form/Html generation?
#TODO: Edit view - reuse Form from add view.


def render(filepath,context):
    """Renders templates to string with passed dict, something like render_to_response.
    :param filepath: Path to template path.
    :type  filepath: str
    :param context: Dict with variables for templates.
    :type context: dict
    :return: Rendered template string.
    :rtype: str 
    """
    with open(filepath) as f:
        template = Template(f.read())
        context_object = Context(context)
        return template.render(context_object)
    

class Decorator(object):
    """This class allows to implement delegation via compostion insted 
    inhritence in easy way. It propagates request to encapsulated object.
    When metohods exist in delegate object it will prefered then delegating.
    See pydoc __getattr__."""
    def __init__(self,instance):
        self._instance = instance
        
    def __getattr__(self,name):
        return getattr(self._instance,name)
    

class FieldWrapper(Decorator):
    def __init__(self,field):
        super(FieldWrapper,self).__init__(field)
        
    def is_foreign(self):
        return isinstance(self._instance,models.ForeignKey)
    
    def type(self):
        return self._instance.__class__.__name__
    
    def relation_to_class(self):
        if self.is_foreign():
            return self._instance.rel.to.__name__
        
        
        
class ModelWrapper(object):
    """Wrapper for Django model objects that translates field 
       with '_' to normal public names. Filed with underscores 
       are not visible in templates."""
    def __init__(self,model):
        self.model = model
        self.fields = self.__get_fields()
        
    @property
    def name(self):
        return self.model.__name__
    
    def __get_fields(self):
        """Translates fields to list of simple dict."""
        field_list = []
        for field in self.model._meta.fields:
            if field.name == 'id':
                continue #skip id field
            else:
                field_list.append(FieldWrapper(field))
        return field_list        
    

def generate_app(template_path,app):
    models = cache.get_models(app)
    context = {"models"   : map(lambda model:ModelWrapper(model),models),
                "app_name" : app.__name__ }
    return render(template_path,context)

def generate(template_path):
    """Generates code based on template_path for all apps that were
        imported to global scope.""" 
    code = ""
    apps = filter(lambda app: not app.__name__.startswith('django'),cache.get_apps())
    for app in apps:
        code += generate_app(template_path,app)
    return code



            
            
# Example call: python generator.py templates/rest.template ../Nile
if __name__ == '__main__':
    parser = OptionParser(usage="Use generator.py <path_to_template> <package_to_settings>")
    parser.add_option("-a", "--app", dest="app",
                  help="Package to source Django app.")
    (options, args) = parser.parse_args()
    
    if len(args) != 2:
        parser.print_help()
        sys.exit()
    #This lines are in main context
    #to make cache globally visible.
    sys.path.append(args[1])
    #TODO: Replace __import__ with importlib.
    settings = __import__("settings")
    setup_environ(settings)
    from django.db.models.loading import cache
    from django.template import Template,Context 
    from django.db import models
    
    if options.app:
        print generate_app(args[0],cache.get_app(options.app))
    else:
        print generate(args[0])